/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


package internal.api.a1;


import api.KieExecutors;
import api.kieservices.KieMarshallers;
import api.kieservices.KieResources;
import api.kieservices.KieScannerFactoryService;
import api.kieservices.KieServices;
import api.kieservices.KieStoreServices;
import api.kieservices.Service;
import internal.impl.KieAssemblersImpl;
import internal.impl.KieBeliefsImpl;
import internal.impl.KieRuntimesImpl;
import internal.impl.KieWeaversImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.misc.ClassLoaderUtil;

import java.net.URL;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;

/**
 * This is an internal class, not for public consumption.
 */
public class ServiceRegistryImpl
        implements
        ServiceRegistry {
    public final String fileName = "kie.conf";
    public final String path =  "META-INF/" + fileName;

    private static final ServiceRegistry          instance        = new ServiceRegistryImpl();

    protected static final transient Logger logger          = LoggerFactory.getLogger(ServiceRegistryImpl.class);

    private final Map<String, Callable< ? >>      registry        = new HashMap<String, Callable< ? >>();
    private final Map<String, Callable< ? >>      defaultServices = new HashMap<String, Callable< ? >>();

    public static ServiceRegistry getInstance() {
        return instance;
    }

    public ServiceRegistryImpl() {
        init();
    }

    /* (non-Javadoc)
     * @see org.kie.util.internal.ServiceRegistry#registerLocator(java.lang.String, java.util.concurrent.Callable)
     */
    public synchronized void registerLocator(Class cls,
                                             Callable cal) {
        this.registry.put( cls.getName(),
                           cal );
    }

    /* (non-Javadoc)
     * @see org.kie.util.internal.ServiceRegistry#unregisterLocator(java.lang.String)
     */
    public synchronized void unregisterLocator(Class cls) {
        this.registry.remove( cls.getName() );
    }

    synchronized void registerInstance(Service service,
                                       Map map) {
        //this.context.getProperties().put( "org.dr, value )
        logger.info( "regInstance : " + map );
        String[] values = (String[]) map.get( "objectClass" );

        for ( String v : values ) {
            logger.info( v );
        }
        // logger.info( "register : " + service );
        this.registry.put( service.getClass().getInterfaces()[0].getName(),
                           new ReturnInstance<Service>( service ) );
    }

    /* (non-Javadoc)
     * @see org.kie.util.internal.ServiceRegistry#unregisterLocator(java.lang.String)
     */
    synchronized void unregisterInstance(Service service,
                                         Map map) {
        logger.info( "unregister : " + map );
        String name = service.getClass().getInterfaces()[0].getName();
        this.registry.remove( name );
        this.registry.put( name,
                           this.defaultServices.get( name ) );
    }

    public synchronized <T> T get(Class<T> cls) {

        Callable< ? > cal = this.registry.get( cls.getName() );
        if ( cal != null ) {
            try {
                return cls.cast( cal.call() );
            } catch ( Exception e ) {
                throw new IllegalArgumentException( "Unable to instantiate service for Class '" + (cls != null ? cls.getName() : null) + "'",
                                                    e );
            }
        } else {
            cal = this.defaultServices.get( cls.getName() );
            try {
                return cls.cast( cal.call() );
            } catch ( Exception e ) {
                throw new IllegalArgumentException( "Unable to instantiate service for Class '" + (cls != null ? cls.getName() : null) + "'",
                                                    e );
            }
        }
    }

    private void init() {
        addDefault( "internal.api.a1.KnowledgeBuilderFactoryService",
                    "compiler.impl.p3.KnowledgeBuilderFactoryServiceImpl" );

        addDefault( "org.kie.api.builder.KnowledgeContainerFactoryService",
                    "org.drools.core.builder.impl.KnowledgeContainerFactoryServiceImpl" );

        addDefault( "internal.api.a2.KnowledgeBaseFactoryService",
                    "core.impl.p8.KnowledgeBaseFactoryServiceImpl" );

        addDefault( KieResources.class,
                    "core.impl.p7.ResourceFactoryServiceImpl" );

        addDefault(  KieMarshallers.class,
                     "core.impl.p7.MarshallerProviderImpl");
        addDefault(  KieExecutors.class,
                     "core.impl.p8.ExecutorProviderImpl");
        addDefault(  KieServices.class,
                     "compiler.impl.p0.KieServicesImpl");
        addDefaultFactory( KieScannerFactoryService.class,
                           "ci.impl.p0.KieScannerFactoryServiceImpl");
        addDefault( KieStoreServices.class,
                    "persistence.impl.p0.KnowledgeStoreServiceImpl");
        addDefault( CorrelationKeyFactory.class,
                    "org.jbpm.persistence.correlation.JPACorrelationKeyFactory");
        addDefault( ClassLoaderResolver.class,
                    "ci.impl.p0.MavenClassLoaderResolver" );
        addDefault( ServiceDiscovery.class,
                    "core.impl.p8.ServiceDiscoveryImpl" );


        defaultServices.put( KieAssemblers.class.getName(),
                             new ReturnInstance( new KieAssemblersImpl()) );
        defaultServices.put( KieWeavers.class.getName(),
                             new ReturnInstance( new KieWeaversImpl()) );
        defaultServices.put( KieRuntimes.class.getName(),
                             new ReturnInstance( new KieRuntimesImpl()) );
        defaultServices.put( KieBeliefs.class.getName(),
                             new ReturnInstance( new KieBeliefsImpl()) );

        initServiceDiscovery();
    }

    private void initServiceDiscovery() {
        Enumeration<URL> confResources = null;
        try {
            confResources = getClassLoader().getResources( path );
        } catch ( Exception e ) {
            // no conf file or no ServiceDiscovery - ignore
        }
        if (confResources != null && confResources.hasMoreElements()) {
            get( ServiceDiscovery.class ).discoverFactories( confResources, this );
        }
    }

    public synchronized void addDefault(Class cls,
                                        String impl) {
        addDefault(cls.getName(), impl);
    }

    private synchronized void addDefault(String service,
                                        String impl) {
        ReflectionInstantiator<Service> resourceRi = new ReflectionInstantiator<Service>( impl );
        defaultServices.put( service,
                             resourceRi );
    }

    public synchronized void addDefaultFactory(Class cls,
                                               String impl) {
        addDefaultFactory(cls.getName(), impl);
    }

    private synchronized void addDefaultFactory(String service,
                                                String impl) {
        FactoryInstantiator<Service> resourceRi = new FactoryInstantiator<Service>( impl );
        defaultServices.put(service,
                            resourceRi);
    }

    public static class ReflectionInstantiator<V>
            implements
            Callable<V> {
        private final String name;

        public ReflectionInstantiator(String name) {
            this.name = name;
        }

        public V call() throws Exception {
            return (V) newInstance( name );
        }

        static <T> T newInstance(String name) {
            try {
                Class<T> cls = (Class<T>) Class.forName( name );
                return cls.newInstance();
            } catch ( Exception e2 ) {
                throw new IllegalArgumentException( "Unable to instantiate '" + name + "'",
                                                    e2 );
            }
        }
    }

    public static class FactoryInstantiator<V>
            implements
            Callable<V> {
        private final String name;
        private final AtomicReference<V> service = new AtomicReference<V>();

        public FactoryInstantiator(String name) {
            this.name = name;
        }

        public V call() throws Exception {
            if (service.get() == null) {
                try {
                    Class<V> cls = (Class<V>) Class.forName( name );
                    service.compareAndSet(null, cls.newInstance());
                } catch ( Exception e ) {
                    throw new IllegalArgumentException( "Unable to instantiate '" + name + "'",
                                                        e );
                }
            }
            return service.get();
        }
    }

    public static class ReturnInstance<V>
            implements
            Callable<V> {
        private final Service service;

        public ReturnInstance(Service service) {
            this.service = service;
        }

        public V call() throws Exception {
            return (V) service;
        }
    }

    private static ClassLoader getClassLoader() {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        if (cl == null) {
            cl = ClassLoader.getSystemClassLoader();
        }
        if (cl == null) {
            cl = ClassLoaderUtil.class.getClassLoader();
        }
        return cl;
    }
}
